import pandas as pd
import numpy as np
import math
from copy import deepcopy
from utils import plot_anything,left_four_pass_five_round

#均值归一化，映射到0-2，无负值利于比较
def mean_normalize(numlist):
    numdata = len(numlist)
    mean = sum(numlist)/numdata
    maxi =  max(numlist)
    mini = min(numlist)
    range = maxi-mini
    temp =  [(e-mean)/range*2 for e in numlist]
    minival = min(temp)
    temp = [i + abs(minival) for i in temp]  # 整体往上抬升，消除负电压值
    return temp

#计算滑动平均值,rolling_window_size是滑动窗口的大小
def slide_average_value(data,rolling_window_size=100,raise_proportion =3.0):
    dataset = pd.Series(data)
    mov_avg = dataset.rolling(rolling_window_size).mean()  # Calculate moving average
    avg_hr = (np.mean(dataset))
    mov_avg = [avg_hr if math.isnan(x) else x for x in mov_avg]
    mov_avg = [x*raise_proportion for x in mov_avg]
    return mov_avg


#根据滑动平均值获取峰值
def get_peak(data,mov_avg,type):
    window = []
    peaklist = []
    listpos = 0  # We use a counter to move over the different data columns
    if type ==">":
        for datapoint in data:
            rollingmean = mov_avg[listpos]  # Get local mean
            if (datapoint <= rollingmean) and (len(window) <= 1):  # If no detectable R-complex activity -> do nothing
                listpos += 1
            elif (datapoint > rollingmean):  # If signal comes above local mean, mark ROI
                window.append(datapoint)
                listpos += 1
            else:  # If signal drops below local mean -> determine highest point回落到低于均值后选取最大的一个点
                if len(window) > 0:
                    beatposition = listpos - len(window) + (
                        window.index(max(window)))  # Notate the position of the point on the X-axis
                    peaklist.append(beatposition)  # Add detected peak to list
                    window = []  # Clear marked ROI
                    listpos += 1

    if type =="<":
        for datapoint in data:
            rollingmean = mov_avg[listpos]  # Get local mean
            if (datapoint >= rollingmean) and (len(window) <= 1):  # If no detectable R-complex activity -> do nothing
                listpos += 1
            elif (datapoint < rollingmean):  # If signal comes above local mean, mark ROI
                window.append(datapoint)
                listpos += 1
            else:  # If signal drops below local mean -> determine highest point回落到低于均值后选取最大的一个点
                if len(window) > 0:
                    beatposition = listpos - len(window) + (
                        window.index(min(window)))  # Notate the position of the point on the X-axis
                    peaklist.append(beatposition)  # Add detected peak to list
                    window = []  # Clear marked ROI
                    listpos += 1

    return peaklist


def compare_avg_with_peak(peaklist,y_value,mov_avg,threshold,type):
    index = 0
    movelist = []
    for x in peaklist:
        if type==">":
            if y_value[index] > mov_avg[x] * threshold:  # 1.75为阈值
                movelist.append(1)  # 作为检测到体动的标记
            else:
                movelist.append(0)
        elif type=="<":
            if y_value[index] < mov_avg[x] * threshold:
                movelist.append(1)  # 作为检测到体动的标记
            else:
                movelist.append(0)
        index += 1

    return movelist


#检测体动
def detect_movement(dataset,sample_rate,high_mov_avg_raise_proportion,low_mov_avg_raise_proportion,
                    high_peak_detect_proportion,low_peak_detect_proportion):
    dataset = mean_normalize(dataset)
    #std_detect_window_size = left_four_pass_five_round(std_detect_window_size)
    #std= variance_detect(dataset,sample_rate,window_size=std_detect_window_size)#检测峰值变异性来检测是否有规律

    high_mov_avg = slide_average_value(dataset,sample_rate,high_mov_avg_raise_proportion)   #检测高峰的滑动平均值
    low_mov_avg =slide_average_value(dataset,sample_rate,low_mov_avg_raise_proportion)

    high_peaklist = get_peak(dataset,high_mov_avg,">")#高峰的时间序列
    low_peaklist =get_peak(dataset,low_mov_avg,"<")

    high_y_value = [dataset[x] for x in high_peaklist] #与时间序列对应的值
    low_y_value = [dataset[x] for x in low_peaklist]

    high_movelist=compare_avg_with_peak(high_peaklist,high_y_value,high_mov_avg,high_peak_detect_proportion,">")#与时间序列对应的体动序列
    low_movelist = compare_avg_with_peak(low_peaklist,low_y_value,low_mov_avg,low_peak_detect_proportion,"<")

    #合并高低峰两个体动序列
    movelist=[]#二维数组，第一个为时间点，第二个为0或1表示有无体动
    for i in range(len(high_movelist)):
        movelist.append([high_peaklist[i], high_movelist[i]])
    for i in range(len(low_movelist)):
        movelist.append([low_peaklist[i],low_movelist[i]])

    movelist=sorted(movelist, key=lambda x: (x[0], x[1]))

    return movelist

def patch_move_list(move_list: list, original_list_length: int):
    result = [0] * original_list_length
    for each in move_list:
        result[each[0]] = each[1]

    temp_start = -1
    for index, each in enumerate(move_list):
        if each[1] == 1:
            if (index + 1 == len(move_list) and move_list[index-1][1] == 0) or \
               (index - 1 == -1 and move_list[index+1][1] == 0) or \
               (move_list[index-1][1] == 0 and move_list[index+1][1] == 0):
               result[each[0]] = 0
               continue
            if temp_start == -1:
                temp_start = each[0] + 1
            else:
                while temp_start <= each[0]:
                    result[temp_start] = 1
                    temp_start += 1
        else:
            if temp_start != -1:
                temp_start = -1
    return result


def smooth_body_movements(
        body_movements: list, sample_rate: int,
        min_duration_threshold: float, is_plot: bool = False,
) -> list:
    """
    合并相邻的体动点/子序列
    body_movements:         list of body movements
    sample_rate:            sample rate of body movement list(Hz)
    min_duration_threshold: (*)choose an appropriate threshold is critical
                            体动最短持续时间(sec)
    is_plot:                draw comparation of smoothing or not
    @return: body movements list after smoothing
    """
    inner_body_movements = deepcopy(body_movements)
    min_duration_threshold = float(min_duration_threshold)
    min_duration_threshold = left_four_pass_five_round(min_duration_threshold * sample_rate)

    flag = 0
    temp_start = 0
    moving = False
    list_length = len(inner_body_movements)
    while flag < list_length:
        if inner_body_movements[flag] == 0:
            if moving is True:
                check_boundry = flag + min_duration_threshold if (flag + min_duration_threshold) <= list_length else list_length
                temp_start = flag
                flag += 1
                while flag < check_boundry:
                    if inner_body_movements[flag] == 1:
                        while temp_start < flag:
                            inner_body_movements[temp_start] = 1
                            temp_start += 1
                        break
                    else:
                        flag += 1
                if flag == check_boundry:
                    moving = False
        else:
            moving = True
        flag += 1

    if is_plot is True:
        plot_anything([body_movements, inner_body_movements], True)
    return inner_body_movements


def remove_abrupt_change_points_in_body_movements(
    body_movements: list, sample_rate: int,
    min_valid_threshold: float, is_plot: bool=False,
) -> list:
    """
    去除体动序列中的突变点/子序列
    body_movements:      list of body movements
    sample_rate:         sample rate of body movement list(Hz)
    min_valid_threshold: (*) choose an appropriate threshold is critical
                         体动最短持续时间(sec)
    is_plot:             draw comparation figure of removing abruptly change points or not
    @return: body movements list after removing abruptly change points
    """
    inner_body_movements = deepcopy(body_movements)
    list_length = len(inner_body_movements)
    min_valid_threshold = float(min_valid_threshold)
    min_valid_threshold = left_four_pass_five_round(min_valid_threshold * sample_rate)

    i = 0
    observer = 0
    temp_start = 0
    while i < list_length:
        if inner_body_movements[i] == 0:
            if observer > 0 and observer < min_valid_threshold:
                while temp_start < i:
                    inner_body_movements[temp_start] = 0
                    temp_start += 1
            observer = 0
        else:
            prev = i - 1
            if prev == -1 or inner_body_movements[prev] == 0:
                temp_start = i
            observer += 1
        i += 1

    # if is_plot is True:
    #     plot_anything([body_movements, inner_body_movements], True)
    return inner_body_movements


def get_body_movements_from_raw_data(
    raw_data: list, sample_rate: int, high_mov_avg_raise_proportion: float,
    low_mov_avg_raise_proportion: float, high_peak_detect_proportion: float,
    low_peak_detect_proportion: float,smooth_duration_threshold,remove_invalid_threshold
) -> list:
    """
    从原始数据中计算出体动01序列[1表示体动，0表示静止](调用宇杰实现)
    """
    temp_data = deepcopy(raw_data)
    move_list = detect_movement(
        temp_data, sample_rate, high_mov_avg_raise_proportion,
        low_mov_avg_raise_proportion, high_peak_detect_proportion,low_peak_detect_proportion
    )
    # for index in range(0, len(move_list), 5000):
    #     print(move_list[index])
    move_list = patch_move_list(move_list, len(temp_data))
    smoothed_body = smooth_body_movements(move_list, sample_rate, smooth_duration_threshold)
    removed = remove_abrupt_change_points_in_body_movements(smoothed_body, sample_rate, remove_invalid_threshold)
    return removed

#计算最后体动占比
def get_body_movement_ratio(movelist):
    move_num =0
    for i in movelist:
        if i ==1:
            move_num +=1
    return move_num/len(movelist)